package simmar.xcs;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Map.Entry;

import simmar.utilities.Helpers;
import simmar.utilities.Logger;

public class XCSPerformance implements XCSConfig
{
    private static Random randomNumber = new Random(System.currentTimeMillis());

    /**
     * 
     * @param timestamp
     * @param condition
     * @param population
     * @return
     */
    protected static List<XCSClassifier> generateMatchSet(int timestamp, int[] condition, Map<String, XCSClassifier> population)
    {
//        System.out.println("---------------------------------------------------------------------------------------------------------");
//        Logger.log("**** XCS GENERATE MATCH SET ****\n");
//        System.out.println("---------------------------------------------------------------------------------------------------------");

        // Initialize match set
        List<XCSClassifier> matchSet = new ArrayList<XCSClassifier>();

        // Check for matches
        while (matchSet.size() == 0)
        {
            Iterator<Entry<String, XCSClassifier>> it = population.entrySet().iterator();

            while (it.hasNext())
            {
                XCSClassifier nextClassifier = it.next().getValue();

                if (conditionMatch(nextClassifier.getRule().getCondition(), condition))
                {
//                    Logger.logLine("Classifier " + nextClassifier.getRule().toString() + " added to match set");
                    matchSet.add(nextClassifier);
                }
            }

            // Make list of unique actions in match set
            Set<Action> uniqueActionsInMatchSet = new HashSet<Action>();

            for (int i = 0; i < matchSet.size(); i++)
            {
                uniqueActionsInMatchSet.add(matchSet.get(i).getRule().getAction());
//                Logger.logLine("Unique action: " + matchSet.get(i).getRule().getAction().toString());
            }

            // Perform covering if necessary
            if (uniqueActionsInMatchSet.size() < COVERING_THRESHOLD)
            {
                XCSClassifier coveringClassifier = generateCoveringClassifier(condition, uniqueActionsInMatchSet, timestamp);
                
                if (XCS_DEBUG_LEVEL.compareTo(DEBUG_LEVEL.NORMAL) > 0)
                    Logger.logLine("Adding covering classifier:\n" + coveringClassifier.toString());
                
                population.put(coveringClassifier.getRule().toString(), coveringClassifier);
                XCSEdit.deleteClassifier(population);
                matchSet.clear();
            }
        }
        
        if (XCS_DEBUG_LEVEL.compareTo(DEBUG_LEVEL.NORMAL) > 0)
        {
            Logger.logLine("Match set is:\n");
            
            for (int i = 0; i < matchSet.size(); i++)
            {
                Logger.log(matchSet.get(i).getRule().toString() + "\n\n");
            }
        }

        return matchSet;
    }

    /**
     * 
     * @param conditionA
     * @param conditionB
     * @return
     */
    protected static boolean conditionMatch(int[] conditionA, int[] conditionB)
    {
//        System.out.println("---------------------------------------------------------------------------------------------------------");
//        Logger.log("**** XCS CONDITION MATCH ****\n");
//        System.out.println("---------------------------------------------------------------------------------------------------------");

        for (int i = 0; i < XCSClassifier.CONDITION_SIZE; i++)
            if (conditionA[i] != -1 && conditionA[i] != conditionB[i])
                return false;
        
        return true;
    }

    /**
     * 
     * @param inputCondition
     * @param uniqueActionsInMatchSet
     * @param timestamp
     * @return
     */
    protected static XCSClassifier generateCoveringClassifier(int[] inputCondition, Set<Action> uniqueActionsInMatchSet, int timestamp)
    {
//        System.out.println("---------------------------------------------------------------------------------------------------------");
//        Logger.log("**** XCS GENERATE COVERING CLASSIFIER ****\n");
//        System.out.println("---------------------------------------------------------------------------------------------------------");

        XCSClassifier coveringClassifier = new XCSClassifier();

        for (int i = 0; i < XCSClassifier.CONDITION_SIZE; i++)
        {
            if (randomNumber.nextFloat() < WILDCARD_PROBABILITY)
                coveringClassifier.getRule().setConditionElement(i, -1);
            else
                coveringClassifier.getRule().setConditionElement(i, inputCondition[i]);
        }

        Action nextAction = Action.UNDEFINED;

        while (uniqueActionsInMatchSet.contains(nextAction) || nextAction == Action.UNDEFINED)
        {
            nextAction = Helpers.intToAction(randomNumber.nextInt(MARIO_ACTIONS.length));
//            Logger.logLine("Next action: " + nextAction.toString());
        }

        coveringClassifier.getRule().setAction(nextAction);
        coveringClassifier.setPrediction(INITIAL_PREDICTION);
        coveringClassifier.setAccuracy(INITIAL_ACCURACY);
        coveringClassifier.setFitness(INITIAL_FITNESS);
        coveringClassifier.setTimestamp(timestamp);
        coveringClassifier.setActionSetSize(1);
        coveringClassifier.setNumerosity(1);
        
        if (XCS_DEBUG_LEVEL.compareTo(DEBUG_LEVEL.NORMAL) > 0)
            Logger.logLine("New covering classifier: " + coveringClassifier.getRule().toString());

        return coveringClassifier;
    }

    protected static float[] generatePredictionArray(List<XCSClassifier> matchSet)
    {
//        System.out.println("---------------------------------------------------------------------------------------------------------");
//        Logger.log("**** XCS GENERATE PREDICTION ARRAY ****\n");
//        System.out.println("---------------------------------------------------------------------------------------------------------");

        float[] fitnessSumArray = new float[MARIO_ACTIONS.length];
        float[] predictionArray = new float[MARIO_ACTIONS.length];

        for (int i = 0; i < MARIO_ACTIONS.length; i++)
        {
            predictionArray[i] = Float.NaN;
            fitnessSumArray[i] = 0.0f;
        }

        for (int i = 0; i < matchSet.size(); i++)
        {
            int nextActionIndex = matchSet.get(i).getRule().getAction().ordinal();
            float nextPrediction = matchSet.get(i).getPrediction();
            float nextFitness = matchSet.get(i).getFitness();

            if (Float.isNaN(predictionArray[nextActionIndex]))
            {
                predictionArray[nextActionIndex] = nextPrediction * nextFitness;
            } else
            {
                predictionArray[nextActionIndex] += nextPrediction * nextFitness;
            }

            fitnessSumArray[nextActionIndex] += nextFitness;
        }

        for (int i = 0; i < MARIO_ACTIONS.length; i++)
        {
            if (fitnessSumArray[i] != 0.0f)
            {
                predictionArray[i] /= fitnessSumArray[i];
            }
        }

        return predictionArray;
    }

    /**
     * 
     * @param predictionArray
     * @return
     */
    protected static Action selectAction(float[] predictionArray)
    {
//        System.out.println("---------------------------------------------------------------------------------------------------------");
//        Logger.log("**** XCS SELECT ACTION ****\n");
//        System.out.println("---------------------------------------------------------------------------------------------------------");

        // TODO: MAKE CHOICE IN ITERATION 0 RANDOM
        int chosenActionIndex = 0;

        if (XCS_DEBUG_LEVEL.compareTo(DEBUG_LEVEL.NORMAL) > 0)
            Logger.logMethod();

        // Select action
        if (randomNumber.nextFloat() < EXPLORATION_PROBABILITY)
        {// Explore
            if (XCS_DEBUG_LEVEL.compareTo(DEBUG_LEVEL.NORMAL) > 0)
                Logger.log("Exploration - ");

            while (Float.isNaN(predictionArray[chosenActionIndex]))
            {
                chosenActionIndex = randomNumber.nextInt(MARIO_ACTIONS.length);
            }
        } else
        {// Exploit
            if (XCS_DEBUG_LEVEL.compareTo(DEBUG_LEVEL.NORMAL) > 0)
                Logger.log("Exploitation - ");
            
            float bestPrediction = Float.NEGATIVE_INFINITY;

            for (int i = 0; i < MARIO_ACTIONS.length; i++)
            {
                if (!Float.isNaN(predictionArray[i]) && predictionArray[i] > bestPrediction)
                {
                    bestPrediction = predictionArray[i];
                    chosenActionIndex = i;
                }
            }
        }

        if (XCS_DEBUG_LEVEL.compareTo(DEBUG_LEVEL.NORMAL) > 0)
            Logger.log("action chosen: " + Helpers.intToAction(chosenActionIndex).toString() + "\n\n");

        return Helpers.intToAction(chosenActionIndex);
    }

    /**
     * 
     * @param selectedAction
     * @param matchSet
     * @return
     */
    protected static List<XCSClassifier> generateActionSet(Action selectedAction, List<XCSClassifier> matchSet)
    {
//        System.out.println("---------------------------------------------------------------------------------------------------------");
//        Logger.log("**** XCS GENERATE ACTION SET ****\n");
//        System.out.println("---------------------------------------------------------------------------------------------------------");

        List<XCSClassifier> actionSet = new ArrayList<XCSClassifier>();

        for (int i = 0; i < matchSet.size(); i++)
            if (matchSet.get(i).getRule().getAction() == selectedAction)
                actionSet.add(matchSet.get(i));
        
        if (XCS_DEBUG_LEVEL.compareTo(DEBUG_LEVEL.NORMAL) > 0)
        {
            Logger.logLine("Generated action set: \n");

            for (int i = 0; i < actionSet.size(); i++)
                Logger.log(actionSet.get(i).getRule().toString() + "\n\n");

        }
        
        return actionSet;
    }
}